/*
    外置脚本目录
        rootpath /
                    ppp-on              拨号的脚本
                    gprs-power.sh       GPRS上下电的脚本
*/

#include <string>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h> 
#include <termios.h> 
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h> 
#include <linux/if.h>


#define CMD_SIM_EXIST       "AT+CPIN?\r\n"        // 检测SIM卡是否存在
#define CMD_SIM_SIG_STREN   "AT+CSQ\r\n"          //检测信号强度
#define CMD_NET_STAT        "AT+CREG?\r\n"        // 查询注册到网络
#define CMD_SIM_TYPE        "AT+COPS?\r\n"        //查询运营商

#define GPRS_POWER_SH       "gprs-power.sh"
#define PPP_CALL_SH         "ppp-on"

using std::string;
using std::cout;
using std::cin;
using std::endl;

/*****************************************************************************
 * 
 * 串口函数提取
 */

static void set_baud_helper(int baud, struct termios *oldtio)
{
    switch(baud)
    {
    case 300:
        cfsetispeed(oldtio, B300);
        cfsetospeed(oldtio, B300);
        break;	  
    case 600:
        cfsetispeed(oldtio, B600);
        cfsetospeed(oldtio, B600);
        break;	
    case 1200:
        cfsetispeed(oldtio, B1200);
        cfsetospeed(oldtio, B1200);
        break;
    case 2400:
        cfsetispeed(oldtio, B2400);
        cfsetospeed(oldtio, B2400);
        break;
    case 4800:
        cfsetispeed(oldtio, B4800);
        cfsetospeed(oldtio, B4800);
        break;
    case 19200:
        cfsetispeed(oldtio, B19200);
        cfsetospeed(oldtio, B19200);
        break;
    case 38400:
        cfsetispeed(oldtio, B38400);
        cfsetospeed(oldtio, B38400);
        break;
    case 57600:
        cfsetispeed(oldtio, B57600);
        cfsetospeed(oldtio, B57600);
        break;
    case 115200:
        cfsetispeed(oldtio, B115200);
        cfsetospeed(oldtio, B115200);
        break;
    default:
        cfsetispeed(oldtio, B9600);
        cfsetospeed(oldtio, B9600);
        break;
    }
}

// 只改变波特率
int set_baud(int fd, int baud)
{
    struct termios oldtio;
    if  ( tcgetattr(fd, &oldtio)  !=  0) 
    { 
        printf("Serial,get serial attr: %s\n", strerror(errno));
        return -1;
    }
    set_baud_helper(baud, &oldtio);

    tcflush(fd,TCIFLUSH);
    if((tcsetattr(fd,TCSANOW,&oldtio))!=0)
    {
        printf("set serial: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

int set_opt(int fd, int baud,
            unsigned char nBits, char nParity,
            unsigned char nStop)
{
    struct termios newtio;
    memset(&newtio,0, sizeof( newtio ));


    newtio.c_cflag=0;
    newtio.c_iflag=0;

    newtio.c_cflag  |=  CLOCAL | CREAD; 
    newtio.c_cflag &= ~CSIZE; 

    switch( nBits )
    {
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 8:
        newtio.c_cflag |= CS8;
        break;
    }

    switch( nParity )
    {
    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    case 'E': 
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        break;
    case 'N':  

        newtio.c_cflag &= ~PARENB;
        break;
    case 'S':
        //newtio.c_cflag |= PARENB | CS8 | CMSPAR; // new concentrator has no CMSPAR
        newtio.c_cflag |= PARENB | CS8;
        break;
    }

    set_baud_helper(baud, &newtio);

    if( nStop == 1 )
    {
        newtio.c_cflag &=  ~CSTOPB;
    }
    else if ( nStop == 2 )
    {
        newtio.c_cflag |=  CSTOPB;
    }
    newtio.c_cc[VTIME] = 0;
    newtio.c_cc[VMIN]  = 0;
    tcflush(fd,TCIFLUSH);
    if((tcsetattr(fd,TCSANOW,&newtio))!=0)
    {
        printf("set serial: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

int readSerial(int fd, void *buf, size_t count, int timeout)
{
    fd_set rdSet;
    struct timeval tv;

    FD_ZERO(&rdSet);
    FD_SET(fd, &rdSet);

    memset(&tv, 0, sizeof(tv));
    tv.tv_usec = timeout * 50000;

    // 已经读到的字节数
    int hasRead = 0;
    int ret = 0;

    int iCounter = 0;
    //printf("----------- in readSerial ---------------\n");
    do
    {
        ret = select(fd + 1, &rdSet, NULL, NULL, &tv);

        if (ret < 0)
        {
            printf("Serial,read select ret: %d, %s\n", ret, strerror(errno));
        }
        else if (FD_ISSET(fd, &rdSet))
        {
            ret = read(fd, (char*)buf + hasRead, count - hasRead);
            if (ret < 0)
            {
                if ((errno == EAGAIN) ||
                    (errno == EWOULDBLOCK) ||
                    (errno == EINTR))
                {
                    //printf("Serial,read timeout error:%s. ignore ti!!!", strerror(errno));
                    ret = 0;
                }
            }
            else
            {
                hasRead += ret;
            }
        }
        //printf("[%d]-------- ret: %d, hasRead: %d\n", iCounter++, ret, hasRead);
    } while ((ret > 0) && (hasRead < count));


    return hasRead;
}

int writeSerial(int fd, const void *buf, size_t count, int timeout)
{
    if (count <= 0)
    {
        return 0;
    }

    fd_set	wtSet;
    struct timeval	tv;

    FD_ZERO(&wtSet);
    FD_SET(fd, &wtSet);

    memset(&tv, 0, sizeof(tv));
    tv.tv_usec = timeout * 1000;

    int ret = select(fd + 1, NULL, &wtSet, NULL, &tv );

    if (ret < 0)
    {
        printf("Serial,write select ret: %d, %s\n", ret, strerror(errno));
    }
    else if (FD_ISSET(fd, &wtSet))
    {
        ret = write(fd, buf, count);
        if (ret < 0)
        {
            if ((errno == EAGAIN) ||
                (errno == EWOULDBLOCK) ||
                (errno == EINTR))
            {
                //printf("Serial,write timeout error:%s. ignore ti!!!", strerror(errno));
                ret = 0;// no data available
            }
        }
        /*
        else if (ret > 0)
        {
            fsync(fd);
        }
        */
    }

    return ret;
}

/***********************************************************************************************/


class internet
{
private:
    string apn;
    string telephone;
    string user;
    string password;

        
public:
    string tty_dev; 
    string isp_name;
    string root_path;
    internet(string dev,string _apn,string _telephone,string _user="  ",string _password=" "):tty_dev(dev),apn(_apn),telephone(_telephone),user(_user),password(_password){}
    ~internet(){}

    virtual void PowerOn()=0;
    virtual void powerOff()=0;
    virtual int  Check()=0;

    int Open();
    int Close();
    int NetStatus(const char *if_name ,string& msg);
    void SetRootPath(string path){root_path=path;}
};

int internet::Close()
{
	system("killall  pppd 2>/dev/null");
}

int internet::Open()
{
    string script_ppp;
    script_ppp.append(root_path+"/"+PPP_CALL_SH+" ");
    script_ppp.append(apn);
    script_ppp.append(" ");
    script_ppp.append(user);
    script_ppp.append(" ");
    script_ppp.append(password);
    script_ppp.append(" ");
    script_ppp.append(telephone);
    script_ppp.append(" & ");
	Close();
	system(script_ppp.c_str());
}



int internet::NetStatus(const char *if_name,string& msg )
{
	int fd = -1; 
	struct ifreq ifr; 
	struct ifconf ifc;  
	struct ifreq ifrs_buf[100]; 
	int if_number =0;
	int i;
 
	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		//fprintf(stderr, "%s: socket error [%d] %s\r\n",if_name, errno, strerror(errno));
        msg="socket error";
		close(fd);
		return -1; 
	}
 
	ifc.ifc_len = sizeof(ifrs_buf);  
	ifc.ifc_buf = (caddr_t)ifrs_buf;  
	if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) <0)  
	{
		//fprintf(stderr, "%s: ioctl SIOCGIFCONF error [%d] %s\r\n",if_name, errno, strerror(errno));
        msg="ioctl SIOCGIFCONF error";
		close(fd);
		return -1; 
	}
 
	if_number = ifc.ifc_len / sizeof(struct ifreq);
	for(i=0; i< if_number; i++)
	{
		if(strcmp(if_name,ifrs_buf[i].ifr_name ) == 0)
		{
			break;
		}
	}
	if(i >= if_number)
	{
		close(fd);
        msg="DEVICE_NONE";
		return 0;
	}
	
	bzero(&ifr, sizeof(ifr));
	strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1); 
	ifr.ifr_name[IFNAMSIZ-1] = 0; 
	if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) <0)  
	{
		//fprintf(stderr, "%s: ioctl SIOCGIFFLAGS error [%d] %s\r\n",if_name, errno, strerror(errno));
        msg="ioctl SIOCGIFFLAGS error";
		close(fd);
		return -1; 
	}	
	if(!(ifr.ifr_flags & IFF_UP))
	{
		close(fd);
		//fprintf(stderr, "DEVICE_DOWN\r\n");
        msg="DEVICE_DOWN";
		return 1;
	}
	if(!(ifr.ifr_flags & IFF_RUNNING))
	{
		close(fd);
		//fprintf(stderr, "DEVICE_UNPLUGGED\r\n");
        msg="DEVICE_UNPLUGGED";
		return 2 ;
	}
	//printf("%s Linked\r\n",if_name);
    msg="Linked";
	return 3;
}



class GprsInternet:public internet
{
private:
    int pin_power;
    int pin_rst;
    int pin_test;
    int PppAtCheck(int fd, const char *cmd,  char *rdBuf,int len =256, int wait=10);

public:
    GprsInternet(string dev,string _apn,string _telephone,string _user="  ",string _password=" "):internet(dev,_apn,_telephone,_user,_password){}
    ~GprsInternet(){}

    void PowerOn();
    void powerOff();
    int Check();
};



int GprsInternet::PppAtCheck(int fd, const char *cmd,  char *rdBuf,int len, int wait)
{
    memset(rdBuf, 0, len);
    int n = writeSerial(fd, cmd, strlen(cmd), wait);
    n = readSerial(fd, rdBuf, len, wait);
    if (n > 0)
    {
        rdBuf[len-1] = 0;
        //printf("%s\n",rdBuf);
        return n;
    }
    return 0;
}

void GprsInternet::PowerOn()
{
    if(!root_path.empty())
        system((root_path+"/"+GPRS_POWER_SH+" on ").c_str());

}
void GprsInternet::powerOff()
{
    if(!root_path.empty())
        system((root_path+"/"+GPRS_POWER_SH+" off ").c_str());
}

int GprsInternet::Check()
{
    int fd = open(tty_dev.c_str(), O_RDWR|O_NOCTTY|O_NDELAY);
    int baud    = 115200;
    set_opt(fd, baud, 8, 'N', 1);

    if(fd<=0)
        return -1;

	char ackbuf[128]={0};
	const char *delim = "\r\n";
	char *saveptr = NULL;
	char *str = NULL;
	int csq = -1;
	
	int faild_try=60;
	while(faild_try--)
	{
		printf("Check 2G/3G/4G moulde\n");
		isp_name.clear();
		// 检测SIM卡是否存在
		if(PppAtCheck(fd, CMD_SIM_EXIST, ackbuf, sizeof(ackbuf)))
		{
			if (NULL != strstr(ackbuf, "READY"))
			{
				printf("SIM Card Ok\n");
                sleep(1);
				// 检测信号强度
				if(PppAtCheck(fd, CMD_SIM_SIG_STREN, ackbuf, sizeof(ackbuf)))
				{
					char csqbuf[32];
					str = strtok_r(ackbuf, delim, &saveptr);
					while(str)
					{
						if (strstr(str, "CSQ:"))
						{
							memset(csqbuf, 0, sizeof(csqbuf));
							strncpy(csqbuf, &str[6], 2);
							csq = atoi(csqbuf);
							break;
						}
						str = strtok_r(NULL, delim, &saveptr);
					}
					if(csq>=10 && csq<99)
					{
						printf("SIG=%2d OK\n",csq);
						// 查询注册到网络
						if(PppAtCheck(fd, CMD_NET_STAT, ackbuf, sizeof(ackbuf)))
						{
							int  creg = 0;
							str = strtok_r(ackbuf, delim, &saveptr);
							while(str)
							{
								if (strstr(str, "CREG:"))
								{
									char cregbuf[16];
									memset(cregbuf, 0, sizeof(cregbuf));
									strncpy(cregbuf, &str[9], 2);
									creg = atoi(cregbuf);
									break;
								}

								str = strtok_r(NULL, delim, &saveptr);
							}
							//1 Registered, home network
							//5 Registered, roaming 漫游 
							if ((1 == creg) || (5 == creg))
							{
								// 查询运营商,后续可以自动寻找APN从文件读取
								if(PppAtCheck(fd, CMD_SIM_TYPE, ackbuf, sizeof(ackbuf)))
								{
									str = strtok_r(ackbuf, delim, &saveptr);
									while(str)
									{
										if (strstr(str, "+COPS:"))
										{
											int fst=0,i=0;
											for(;i<strlen(str),isp_name.length()<128;i++)
											{
												if(str[i]=='\"')
												{
													if(fst==0) {fst=1;continue;}
													else break;
												}
												if(fst==1)
												{
                                                    isp_name=isp_name+str[i];
												}
											}
										}
										str = strtok_r(NULL, delim, &saveptr);
									}
									if(isp_name.length()>0)
                                    {
                                        close(fd);
                                        printf("ISP=%s \n",isp_name.c_str());
                                        return csq;
                                    }
								}
							}
						}	
					}
				}
			}
		}
        sleep(2);
	}
    close(fd);
	return csq;
}



int main(int argc, const char** argv) {

    // 1. 检查ppp0是否存在,如果存在先kill
    // 2. 复位模块
    // 3. 检查卡,信号等
    // 4. 拨号
    GprsInternet uc15("/dev/ttymxc5","internet","*99***1#");
    uc15.SetRootPath("./ppp/");

    string msg;
    if(uc15.NetStatus("ppp0",msg)==3)
    {
        uc15.Close();
    }

    uc15.powerOff();
    sleep(1);
    uc15.PowerOn();

    if(uc15.Check()>0)
        uc15.Open();

    int ret=-1;
    for(int i=0;i<10;i++,sleep(2))
    {
        ret=uc15.NetStatus("ppp0",msg);
        cout<< msg<<endl;
        if(ret==3)
        {
            break;
        }
    }
    return ret;
}